/*
 * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef ACORE_SPELLAURAS_H
#define ACORE_SPELLAURAS_H

#include "SpellAuraDefines.h"
#include "Unit.h"

class Unit;
class SpellInfo;
struct SpellModifier;
struct ProcTriggerSpell;
struct SpellProcEntry;

// forward decl
class AuraEffect;
class Aura;
class DynamicObject;
class AuraScript;

class AuraApplication
{
    friend void Unit::_ApplyAura(AuraApplication* aurApp, uint8 effMask);
    friend void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveMode removeMode);
    friend void Unit::_ApplyAuraEffect(Aura* aura, uint8 effIndex);
    friend void Unit::RemoveAura(AuraApplication* aurApp, AuraRemoveMode mode);
    friend AuraApplication* Unit::_CreateAuraApplication(Aura* aura, uint8 effMask);
private:
    Unit* const _target;
    Aura* const _base;
    AuraRemoveMode _removeMode: 8;                 // Store info for know remove aura reason
    uint8 _slot;                                   // Aura slot on unit
    uint8 _flags;                                  // Aura info flag
    uint8 _effectsToApply;                         // Used only at spell hit to determine which effect should be applied
    bool _needClientUpdate: 1;

    // xinef: stacking
    uint8 _disableMask;

    explicit AuraApplication(Unit* target, Unit* caster, Aura* base, uint8 effMask);
    void _Remove();
private:
    void _InitFlags(Unit* caster, uint8 effMask);
    void _HandleEffect(uint8 effIndex, bool apply);
public:
    Unit* GetTarget() const { return _target; }
    Aura* GetBase() const { return _base; }

    uint8 GetSlot() const { return _slot; }
    uint8 GetFlags() const { return _flags; }
    uint8 GetEffectMask() const { return _flags & (AFLAG_EFF_INDEX_0 | AFLAG_EFF_INDEX_1 | AFLAG_EFF_INDEX_2); }
    bool HasEffect(uint8 effect) const { ASSERT(effect < MAX_SPELL_EFFECTS);  return _flags & (1 << effect); }
    bool IsPositive() const { return _flags & AFLAG_POSITIVE; }
    bool IsSelfcasted() const { return _flags & AFLAG_CASTER; }
    uint8 GetEffectsToApply() const { return _effectsToApply; }

    void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; }
    AuraRemoveMode GetRemoveMode() const {return _removeMode;}

    void SetNeedClientUpdate() { _needClientUpdate = true;}
    bool IsNeedClientUpdate() const { return _needClientUpdate;}
    void BuildUpdatePacket(ByteBuffer& data, bool remove) const;
    void ClientUpdate(bool remove = false);

    // xinef: stacking
    bool IsActive(uint8 effIdx) { return ((1 << effIdx) & _disableMask) == 0; }
    void SetDisableMask(uint8 effIdx) { _disableMask |= 1 << effIdx; }
    void RemoveDisableMask(uint8 effIdx) { _disableMask &= ~(1 << effIdx); }
};

class Aura
{
    friend Aura* Unit::_TryStackingOrRefreshingExistingAura(SpellInfo const* newAura, uint8 effMask, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, bool noPeriodicReset);
public:
    typedef std::map<ObjectGuid, AuraApplication*> ApplicationMap;

    static uint8 BuildEffectMaskForOwner(SpellInfo const* spellProto, uint8 avalibleEffectMask, WorldObject* owner);
    static Aura* TryRefreshStackOrCreate(SpellInfo const* spellproto, uint8 tryEffMask, WorldObject* owner, Unit* caster, int32* baseAmount = nullptr, Item* castItem = nullptr, ObjectGuid casterGUID = ObjectGuid::Empty, bool* refresh = nullptr, bool periodicReset = false);
    static Aura* TryCreate(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount = nullptr, Item* castItem = nullptr, ObjectGuid casterGUID = ObjectGuid::Empty, ObjectGuid itemGUID = ObjectGuid::Empty);
    static Aura* Create(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID = ObjectGuid::Empty);
    explicit Aura(SpellInfo const* spellproto, WorldObject* owner, Unit* caster, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID = ObjectGuid::Empty);
    void _InitEffects(uint8 effMask, Unit* caster, int32* baseAmount);
    virtual ~Aura();

    SpellInfo const* GetSpellInfo() const { return m_spellInfo; }
    uint32 GetId() const;

    ObjectGuid GetCastItemGUID() const { return m_castItemGuid; }
    uint32 GetCastItemEntry() const { return m_castItemEntry; }
    ObjectGuid GetCasterGUID() const { return m_casterGuid; }
    Unit* GetCaster() const;
    WorldObject* GetOwner() const { return m_owner; }
    Unit* GetUnitOwner() const { ASSERT(GetType() == UNIT_AURA_TYPE); return (Unit*)m_owner; }
    DynamicObject* GetDynobjOwner() const { ASSERT(GetType() == DYNOBJ_AURA_TYPE); return (DynamicObject*)m_owner; }

    AuraObjectType GetType() const;

    virtual void _ApplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp);
    virtual void _UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* auraApp);
    void _Remove(AuraRemoveMode removeMode);
    virtual void Remove(AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT) = 0;

    virtual void FillTargetMap(std::map<Unit*, uint8>& targets, Unit* caster) = 0;
    void UpdateTargetMap(Unit* caster, bool apply = true);

    void _RegisterForTargets() {Unit* caster = GetCaster(); UpdateTargetMap(caster, false);}
    void ApplyForTargets() {Unit* caster = GetCaster(); UpdateTargetMap(caster, true);}
    void _ApplyEffectForTargets(uint8 effIndex);

    void UpdateOwner(uint32 diff, WorldObject* owner);
    void Update(uint32 diff, Unit* caster);

    time_t GetApplyTime() const { return m_applyTime; }
    int32 GetMaxDuration() const { return m_maxDuration; }
    void SetMaxDuration(int32 duration) { m_maxDuration = duration; }
    int32 CalcMaxDuration() const { return CalcMaxDuration(GetCaster()); }
    int32 CalcMaxDuration(Unit* caster) const;
    int32 GetDuration() const { return m_duration; }
    void SetDuration(int32 duration, bool withMods = false);    /// @todo - Look to convert to std::chrono
    void RefreshDuration(bool withMods = false);
    void RefreshTimers(bool periodicReset = false);
    void RefreshTimersWithMods();
    bool IsExpired() const { return !GetDuration();}
    bool IsPermanent() const { return GetMaxDuration() == -1; }

    uint8 GetCharges() const { return m_procCharges; }
    void SetCharges(uint8 charges);
    uint8 CalcMaxCharges(Unit* caster) const;
    uint8 CalcMaxCharges() const { return CalcMaxCharges(GetCaster()); }
    bool ModCharges(int32 num, AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT);
    bool DropCharge(AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT) { return ModCharges(-1, removeMode); }

    uint8 GetStackAmount() const { return m_stackAmount; }
    void SetStackAmount(uint8 num);
    bool ModStackAmount(int32 num, AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT, bool periodicReset = false);

    void RefreshSpellMods();

    uint8 GetCasterLevel() const { return m_casterLevel; }

    bool IsArea() const;
    bool IsPassive() const;
    bool IsDeathPersistent() const;
    bool IsRemovedOnShapeLost(Unit* target) const;
    bool CanBeSaved() const;
    bool IsRemoved() const { return m_isRemoved; }
    bool CanBeSentToClient() const;
    // Single cast aura helpers
    bool IsSingleTarget() const {return m_isSingleTarget; }
    bool IsSingleTargetWith(Aura const* aura) const;
    void SetIsSingleTarget(bool val) { m_isSingleTarget = val; }
    void UnregisterSingleTarget();
    int32 CalcDispelChance(Unit* auraTarget, bool offensive) const;

    void SetLoadedState(int32 maxduration, int32 duration, int32 charges, uint8 stackamount, uint8 recalculateMask, int32* amount);

    // helpers for aura effects
    bool HasEffect(uint8 effIndex) const { return bool(GetEffect(effIndex)); }
    bool HasEffectType(AuraType type) const;
    AuraEffect* GetEffect(uint8 effIndex) const { ASSERT (effIndex < MAX_SPELL_EFFECTS); return m_effects[effIndex]; }
    uint8 GetEffectMask() const { uint8 effMask = 0; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (m_effects[i]) effMask |= 1 << i; return effMask; }
    void RecalculateAmountOfEffects();
    void HandleAllEffects(AuraApplication* aurApp, uint8 mode, bool apply);

    // Helpers for targets
    ApplicationMap const& GetApplicationMap() {return m_applications;}
    void GetApplicationList(std::list<AuraApplication*>& applicationList) const;
    const AuraApplication* GetApplicationOfTarget (ObjectGuid guid) const { ApplicationMap::const_iterator itr = m_applications.find(guid); if (itr != m_applications.end()) return itr->second; return nullptr; }
    AuraApplication* GetApplicationOfTarget (ObjectGuid guid) { ApplicationMap::iterator itr = m_applications.find(guid); if (itr != m_applications.end()) return itr->second; return nullptr; }
    bool IsAppliedOnTarget(ObjectGuid guid) const { return m_applications.find(guid) != m_applications.end(); }

    void SetNeedClientUpdateForTargets() const;
    void HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, bool apply, bool onReapply);
    bool CanBeAppliedOn(Unit* target);
    bool CheckAreaTarget(Unit* target);
    bool CanStackWith(Aura const* checkAura, bool remove) const;
    bool IsAuraStronger(Aura const* newAura) const;

    // Proc system
    // this subsystem is not yet in use - the core of it is functional, but still some research has to be done
    // and some dependant problems fixed before it can replace old proc system (for example cooldown handling)
    // currently proc system functionality is implemented in Unit::ProcDamageAndSpell
    bool IsProcOnCooldown() const;
    void AddProcCooldown(Milliseconds msec);
    bool IsUsingCharges() const { return m_isUsingCharges; }
    void SetUsingCharges(bool val) { m_isUsingCharges = val; }
    void PrepareProcToTrigger(AuraApplication* aurApp, ProcEventInfo& eventInfo);
    bool IsProcTriggeredOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo) const;
    float CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const;
    void TriggerProcOnEvent(AuraApplication* aurApp, ProcEventInfo& eventInfo);

    // AuraScript
    void LoadScripts();
    bool CallScriptCheckAreaTargetHandlers(Unit* target);
    void CallScriptDispel(DispelInfo* dispelInfo);
    void CallScriptAfterDispel(DispelInfo* dispelInfo);
    bool CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
    bool CallScriptEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
    void CallScriptAfterEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
    void CallScriptAfterEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
    bool CallScriptEffectPeriodicHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp);
    void CallScriptEffectUpdatePeriodicHandlers(AuraEffect* aurEff);
    void CallScriptEffectCalcAmountHandlers(AuraEffect const* aurEff, int32& amount, bool& canBeRecalculated);
    void CallScriptEffectCalcPeriodicHandlers(AuraEffect const* aurEff, bool& isPeriodic, int32& amplitude);
    void CallScriptEffectCalcSpellModHandlers(AuraEffect const* aurEff, SpellModifier*& spellMod);
    void CallScriptEffectAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount, bool& defaultPrevented);
    void CallScriptEffectAfterAbsorbHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount);
    void CallScriptEffectManaShieldHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount, bool& defaultPrevented);
    void CallScriptEffectAfterManaShieldHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& absorbAmount);
    void CallScriptEffectSplitHandlers(AuraEffect* aurEff, AuraApplication const* aurApp, DamageInfo& dmgInfo, uint32& splitAmount);

    // Spell Proc Hooks
    bool CallScriptCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo);
    bool CallScriptAfterCheckProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo, bool isTriggeredAtSpellProcEvent);
    bool CallScriptPrepareProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo);
    bool CallScriptProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo);
    void CallScriptAfterProcHandlers(AuraApplication const* aurApp, ProcEventInfo& eventInfo);
    bool CallScriptEffectProcHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo);
    void CallScriptAfterEffectProcHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, ProcEventInfo& eventInfo);

    AuraScript* GetScriptByName(std::string const& scriptName) const;

    std::list<AuraScript*> m_loadedScripts;

    virtual std::string GetDebugInfo() const;

    void SetTriggeredByAuraSpellInfo(SpellInfo const* triggeredByAuraSpellInfo);
    SpellInfo const* GetTriggeredByAuraSpellInfo() const;

private:
    void _DeleteRemovedApplications();

protected:
    SpellInfo const* const m_spellInfo;
    ObjectGuid const m_casterGuid;
    ObjectGuid const m_castItemGuid;                    // it is NOT safe to keep a pointer to the item because it may get deleted
    uint32 const m_castItemEntry;                       // when deleted, we could retrieve some information from template instead
    time_t const m_applyTime;
    WorldObject* const m_owner;

    int32 m_maxDuration;                                // Max aura duration
    int32 m_duration;                                   // Current time
    int32 m_timeCla;                                    // Timer for power per sec calcultion
    int32 m_updateTargetMapInterval;                    // Timer for UpdateTargetMapOfEffect

    uint8 const m_casterLevel;                          // Aura level (store caster level for correct show level dep amount)
    uint8 m_procCharges;                                // Aura charges (0 for infinite)
    uint8 m_stackAmount;                                // Aura stack amount

    AuraEffect* m_effects[3];
    ApplicationMap m_applications;

    bool m_isRemoved: 1;
    bool m_isSingleTarget: 1;                       // true if it's a single target spell and registered at caster - can change at spell steal for example
    bool m_isUsingCharges: 1;

private:
    Unit::AuraApplicationList m_removedApplications;

    SpellInfo const* m_triggeredByAuraSpellInfo;
};

class UnitAura : public Aura
{
    friend Aura* Aura::Create(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID);

protected:
    explicit UnitAura(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID = ObjectGuid::Empty);

public:
    void _ApplyForTarget(Unit* target, Unit* caster, AuraApplication* aurApp) override;
    void _UnapplyForTarget(Unit* target, Unit* caster, AuraApplication* aurApp) override;

    void Remove(AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT) override;

    void FillTargetMap(std::map<Unit*, uint8>& targets, Unit* caster) override;

    // Allow Apply Aura Handler to modify and access m_AuraDRGroup
    void SetDiminishGroup(DiminishingGroup group) { m_AuraDRGroup = group; }
    DiminishingGroup GetDiminishGroup() const { return m_AuraDRGroup; }

private:
    DiminishingGroup m_AuraDRGroup: 8;              // Diminishing
};

class DynObjAura : public Aura
{
    friend Aura* Aura::Create(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID);

protected:
    explicit DynObjAura(SpellInfo const* spellproto, uint8 effMask, WorldObject* owner, Unit* caster, int32* baseAmount, Item* castItem, ObjectGuid casterGUID, ObjectGuid itemGUID = ObjectGuid::Empty);

public:
    void Remove(AuraRemoveMode removeMode = AURA_REMOVE_BY_DEFAULT) override;

    void FillTargetMap(std::map<Unit*, uint8>& targets, Unit* caster) override;
};
#endif
